/*
 * vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4 foldmethod=marker:
 *
 * rotatedlogs.c
 *
 * C.C.Allison
 * chris.allison@hotmail.com
 *
 * Started: Friday 15 February 2013, 07:21:04
 * Version: 0.00
 * Revision: $Id: rotatedlogs.c 58 2013-04-03 09:35:25Z chris.charles.allison@gmail.com $
 * Last Modified: Wednesday  3 April 2013, 09:33:38
 */

#include "rotatedlogs.h"

void freeRotatedLogFileNames(char **rlfnames)/* {{{1 */
{
    int x,c;

    c=CCA_MAX_ROTATED_FILES;
    for(x=0;x<c;x++){
        if(rlfnames[x]){
            free(rlfnames[x]);
        }
    }
    free(rlfnames);
}/* }}} */
char **getRotatedLogFileNames(char *fqlogfilename)/* {{{1 */
{
    int ret;
    char *fql;
    char *fqf;
    char *tmp;
    char *path;
    DIR *dp;
    struct dirent *rd;
    char **rlfiles;
    int whichrlf;
    int len;
    int x,c;

    if((rlfiles=(char **)malloc(sizeof(char *) * CCA_MAX_ROTATED_FILES))==NULL){
        CCAE(1,"out of memory");
    }
    c=CCA_MAX_ROTATED_FILES;
    for(x=0;x<c;x++){
        rlfiles[x]=NULL;
    }
    whichrlf=0;
    if((fql=strdup(fqlogfilename)) == NULL){
        CCAE(1,"out of memory");
    }
    /* get the path */
    tmp=dirname(fql);
    if((path=strdup(tmp)) == NULL){
        CCAE(1,"out of memory");
    }
    if((dp=opendir(path)) == NULL){
        CCAE(errno,"Unable to open directory");
    }
    while((rd=readdir(dp)) != NULL){
        if((ret=logfileFilter(rd)) == 1){
            len=snprintf(fqf,0,"%s/%s",path,rd->d_name);
            len++;
            if(( fqf=malloc(len)) == NULL){
                CCAE(1,"Out of memory.");
            }
            len=snprintf(fqf,len,"%s/%s",path,rd->d_name);
            rlfiles[whichrlf]=fqf;
            whichrlf++;
            if(whichrlf>=CCA_MAX_ROTATED_FILES){
                /* no more space for filenames available, drop out of the loop */
                break;
            }
            /* note the last pointer available */
            rlfiles[whichrlf]=NULL;
        }
    }
    /* end of directory */
    if((ret=closedir(dp)) == -1){
        CCAE(errno,"Failed to close directory");
    }
    free(path);
    free(fql);
    return rlfiles;
}/* }}} */
void parseRotatedLogs(struct FollowedFile *ff,char **rlfnames)/* {{{1 */
{
    int whichrlf=0;
    int filenum;
    char *holdfn;

    holdfn=ff->filename;
    while(rlfnames[whichrlf]){
        resetFollowFile(ff);
        ff->filename=rlfnames[whichrlf];
        filenum=filenumberFromFilename(rlfnames[whichrlf]);
        parseRotatedLog(ff,filenum);
        // finishReadingRotatedFile(ff,filenum);
        whichrlf++;
        if(whichrlf>=CCA_MAX_ROTATED_FILES){
            break;
        }
    }
    ff->filename=holdfn;
}/* }}} */
void indexRotatedLogs(char **rlfnames)/* {{{1 */
{
    int whichrlf=0;
    struct LogFile *rlf;

    while(rlfnames[whichrlf]){
        rlf=addRotatedLogFile(rlfnames[whichrlf],filenumberFromFilename(rlfnames[whichrlf]));
        indexLogFile(rlf);
        DBGL("filenum:%d:%s:Lines:%ld:min:%d:max:%d:time:%d",rlf->filenum,rlf->filename,rlf->numlines,rlf->minstamp,rlf->maxstamp,rlf->maxstamp-rlf->minstamp);
        whichrlf++;
        if(whichrlf>=CCA_MAX_ROTATED_FILES){
            break;
        }
    }
}/* }}} */
/* unused {{{1
void indexRotatedLogs(char **rlfnames)
{
    int ret=0;
    // int numfiles=0;
    char *path;
    char *tmp;
    DIR *dp;
    struct dirent *rd;
    int len;
    char *fqf;
    char *fql;
    struct LogFile *rlf;
    int filenum;

    if((fql=strdup(fqlogfilename)) == NULL){
        CCAE(1,"out of memory");
    }
    // get the path 
    tmp=dirname(fql);
    if((path=strdup(tmp)) == NULL){
        CCAE(1,"out of memory");
    }
    if((dp=opendir(path)) == NULL){
        CCAE(errno,"Unable to open directory");
    }
    while((rd=readdir(dp)) != NULL){
        if((ret=logfileFilter(rd)) == 1){
            // DBGL("would index %s",rd->d_name);
            len=strlen(rd->d_name);
            filenum=filenumberFromFilename(rd->d_name);
            // DBGL("len:%d:%s",len,rd->d_name);
            // len=strlen(path);
            // DBGL("len:%d:%s",len,path);
            len=snprintf(fqf,0,"%s/%s",path,rd->d_name);
            // for the terminating null 
            len++;
            // DBGL("len:%d: for path/rd->d_name",len);
            if(( fqf=malloc(len)) == NULL){
                CCAE(1,"Out of memory.");
            }
            len=snprintf(fqf,len,"%s/%s",path,rd->d_name);
            // DBGL("len:%d: for path/rd->d_name",len);
            rlf=addRotatedLogFile(fqf,filenum);
            ret=indexLogFile(rlf);
            DBGL("%s:Lines:%ld:min:%d:max:%d:time:%d",rlf->filename,rlf->numlines,rlf->minstamp,rlf->maxstamp,rlf->maxstamp-rlf->minstamp);
            free(fqf);
        }else{
            DBGL("Not indexing %s",rd->d_name);
        }
        errno=0;
    }
    if(errno){
        CCAE(errno,"Unable to read directory");
    }
    // end of directory 
    if((ret=closedir(dp)) == -1){
        CCAE(errno,"Failed to close directory");
    }
    free(path);
    free(fql);
    return ret;
}
}}} */
struct LogFile *addRotatedLogFile( char *filename, int filenum )/* {{{1 */
{
    struct LogFile *rlogfile;
    struct LogFile *trlf;
    struct LogFile *prlf=NULL;

    if((rlogfile=malloc(sizeof(struct LogFile))) == NULL){
        CCAE(1,"out of memory.");
    }
    if((rlogfile->filename=strdup(filename)) == NULL){
        CCAE(1,"out of memory.");
    }
    DBGL("adding rotated record for %s, filenum: %d",filename,filenum);
    rlogfile->filesize=filesize(rlogfile->filename);
    rlogfile->filenum=filenum;
    rlogfile->minstamp=INT_MAX;
    rlogfile->maxstamp=0;
    rlogfile->numlines=0;
    rlogfile->type=filetype(rlogfile->filename);
    rlogfile->next=NULL;
    if(rotatedlogs==NULL){
        /* first one in the list */
        rotatedlogs=rlogfile;
    }else{
            trlf=rotatedlogs;
            while(trlf->filenum<filenum && trlf->next){
                prlf=trlf;
                trlf=trlf->next;
            }
            /*
             * we have either fallen off the end of the list
             * or we need to insert this one before the current list entry
             * between prlf and trlf.  if prlf is null then trlf is the head
             * of the list, so this one becomes the head.
             */
            if(trlf->next){
                /*
                 * this one needs to be inserted into the list at this point
                 */
                if(prlf){
                    prlf->next=rlogfile;
                    rlogfile->next=trlf;
                }else{
                    rlogfile->next=trlf;
                    rotatedlogs=rlogfile;
                }
            }else{
                /* we fell off the end of the list */
                if(trlf->filenum<filenum){
                    /*
                     * this goes after the last one, so becomes the new end of list
                     */
                    trlf->next=rlogfile;
                }else{
                    /*
                     * this goes before the last one
                     */
                    if(prlf){
                        prlf->next=rlogfile;
                        rlogfile->next=trlf;
                    }else{
                        rlogfile->next=trlf;
                        rotatedlogs=rlogfile;
                    }
                }
            }
    }
    return rlogfile;
}/* }}} */
int logfileFilter(const struct dirent *entry)/* {{{1 */
{
    char *follow;
    char *bfollow=NULL;
    // char *tmp;
    int len;
    int ret;

    ret=0;
    if((follow=configValue("follow")) != NULL){
        bfollow=bname(follow); /* see tools.c */
        len=strlen(bfollow);
        if(( ret=strncmp(bfollow,entry->d_name,len)) == 0 ){
            /* 
             * is this the actual file we are following
             * if so don't index it
             */
            if((ret=strcmp(bfollow,entry->d_name)) != 0 ){
                /*
                 * nope, it isn't so index it
                 */
                // DBGL("%s passed",entry->d_name);
                ret=1;
            }
        }
        free(bfollow);
    }
    return ret;
} /* }}} */
FILE *openLogFile(struct LogFile *rlf)/* {{{1 */
{
    FILE *ofp=NULL;

    switch(rlf->type){
        case 1:
            /* text */
            ofp=fopen(rlf->filename,"r");
            break;
        case 2:
            ofp=openGzipFile(rlf->filename);
            break;
        case 3:
            ofp=openZipFile(rlf->filename);
            break;
        default:
            CCAC("Unknown file format: %s",rlf->filename);
            break;
    }
    return ofp;
}/* }}} */
int indexLogFile(struct LogFile *rlf)/* {{{1 */
{
    int ret=0;
    FILE *ofp;

    if((ofp=openLogFile(rlf))){
        indexOpenLogFile(ofp,rlf);
        fclose(ofp);
        ret=rlf->numlines;
    }
    return ret;
}/* }}} */
FILE *openGzipFile(char *filename)/* {{{1 */
{
    int ret;
    int pd[2];
    FILE *ofp;
    int cpid;
    char *uzb;
    char *uzba;

    if((ret=pipe(pd)) != 0){
        CCAE(errno,"Failed to open pipe.");
    }
    if((uzb=configValue("gunzipbin")) == NULL){
        close(pd[1]);
        close(pd[0]);
        return NULL; /* no executable set for the unzip utility */
    }
    uzba=configValue("gunzipbinargs");
    cpid=fork();
    if((cpid==-1)){
        close(pd[1]);
        close(pd[0]);
        CCAE(errno,"out of memory, cannot fork for %s.",uzb);
    }
    if((cpid==0)){
        /* close the input side of the pipe */
        close(pd[0]);
        /* close stdout and duplicate the output side of the pipe */
        dup2(pd[1],STDOUT_FILENO);
        /* exec the unzip utility on the file *filename */
        if(uzba){
            ret=execl(uzb,uzb,uzba,filename,NULL);
        }else{
            ret=execl(uzb,uzb,filename,NULL);
        }
        /* this should never be reached */
        CCAE(1,"Failed to execute the gunzip utility");
    }else{
        /* close the output side of the pipe */
        close(pd[1]);
    }
    ofp=fdopen(pd[0],"r");
    return ofp;
}/* }}} */
FILE *openZipFile(char *filename)/* {{{1 */
{
    int ret;
    int pd[2];
    FILE *ofp;
    int cpid;
    char *uzb;
    char *uzba;

    if((ret=pipe(pd)) != 0){
        CCAE(errno,"Failed to open pipe.");
    }
    if((uzb=configValue("unzipbin")) == NULL){
        close(pd[1]);
        close(pd[0]);
        return NULL; /* no executable set for the unzip utility */
    }
    uzba=configValue("unzipbinargs");
    cpid=fork();
    if((cpid==-1)){
        close(pd[1]);
        close(pd[0]);
        CCAE(errno,"out of memory, cannot fork for %s.",uzb);
    }
    if((cpid==0)){
        /* close the input side of the pipe */
        close(pd[0]);
        /* close stdout and duplicate the output side of the pipe */
        dup2(pd[1],STDOUT_FILENO);
        /* exec the unzip utility on the file *filename */
        if(uzba){
            ret=execl(uzb,uzb,uzba,filename,NULL);
        }else{
            ret=execl(uzb,uzb,filename,NULL);
        }
        /* this should never be reached */
        CCAE(1,"Failed to execute the unzip utility");
    }else{
        /* close the output side of the pipe */
        close(pd[1]);
    }
    ofp=fdopen(pd[0],"r");
    return ofp;
}/* }}} */
int indexOpenLogFile( FILE *ofp, struct LogFile *rlogfile )/* {{{1 */
{
    char *buffer;
    char *str;
    char *format;
    long bufsize;
    time_t logtime;
    int ret=0;

    format=configValue("buffersize");
    bufsize=atol(format);
    format=configValue("timeformat");
    if((buffer=malloc(bufsize)) == NULL){
        CCAE(1,"out of memory.");
    }
    if(ofp){
        while((str=fgets(buffer,bufsize,ofp)) == buffer){
            rlogfile->numlines++;
            logtime=is_logline(str,format);
            if(logtime){
                if(logtime<rlogfile->minstamp){
                    rlogfile->minstamp=logtime;
                }
                if(logtime>rlogfile->maxstamp){
                    rlogfile->maxstamp=logtime;
                }
            }
        }
    }else{
        ret=1;
    }
    free(buffer);
    return ret;
}/* }}} */
int filetype( char *filename )/* {{{1 */
{
    /*
     * uses the file utility to check the type of file
     * returns:
     *       text = 1
     *       gzip = 2
     *        zip = 3
     */
    char *filebin;
    char *filebinargs;
    int pd[2];
    int cpid;
    char *buffer;
    int buflen=4096; /* 4Kb should be sufficient for the output of file */
    // int len;
    // char *tstr;
    int ret;
    int nbytes;
    char slash[]="/";
    char scolon[]=";";
    char ftext[]="text";
    char fzip[]="zip";
    char fgzip[]="x-gzip";
    char *junk;

    if((ret=pipe(pd)) != 0){
        CCAE(errno,"Failed to open pipe.");
    }
    if((filebin=configValue("filebin")) == NULL){
        close(pd[1]);
        close(pd[0]);
        return 0; /* no executable set for the file utility */
    }
    filebinargs=configValue("filebinargs");
    cpid=fork();
    if(cpid==0){
        /* in child */
        /* close the input side of the pipe */
        close(pd[0]);
        /* close stdout and duplicate the output side of the pipe */
        dup2(pd[1],STDOUT_FILENO);
        /* exec the file utility on the file *filename */
        if(filebinargs){
            ret=execl(filebin,filebin,filebinargs,filename,NULL);
        }else{
            ret=execl(filebin,filebin,filename,NULL);
        }
        /* this should never be reached */
        CCAE(1,"Failed to execute the file utility");
    }else{
        if(cpid==-1){
            close(pd[1]);
            close(pd[0]);
            CCAE(errno,"out of memory, cannot fork for filetype.");
        }
        /* in parent */
        /* close the output side of the pipe */
        close(pd[1]);
        if((buffer=malloc(buflen)) == NULL){
            CCAE(errno,"out of memory");
        }
        nbytes=read(pd[0],buffer,buflen);
        buffer[nbytes]='\0';
        // DBGL("file return: %s:%d:%s",filename,nbytes,buffer);
        close(pd[0]);
        /* split up the string returned */
        if((junk=strtok(buffer,slash)) == NULL){
            ret=0; // there is no slash in the output from file
            CCAC("Cannot determine file type for %s. file returned %s",filename,buffer);
        }else{
            if((ret=strcmp(junk,ftext)) == 0){
                // this is a text file
                ret=1;
                // DBGL("file %s is a text file.",filename);
            }else{
                if((junk=strtok(NULL,slash)) == NULL){
                    CCAE(1,"file return had a slash, but now it hasn't??? %s '%s'",filename,buffer);
                }
                if((junk=strtok(junk,scolon)) == NULL){
                    CCAC("Cannot determine file type for %s. file returned %s",filename,buffer);
                    ret=0;
                }else{
                    if((ret=strcmp(junk,fgzip)) == 0){
                        ret=1;
                        // DBGL("file %s is in gzip format.",filename);
                    }else{
                        if((ret=strcmp(junk,fzip)) == 0){
                            ret=2;
                            // DBGL("file %s is in zip format.",filename);
                        }else{
                            // DBGL("file %s is an unknown format. '%s'.",filename,buffer);
                            ret=0;
                        }
                    }
                }
            }
        }
        free(buffer);
    }
    return ret;
}/* }}} */
void parseRotatedLog(struct FollowedFile *ffile,int filenum)/* {{{1 */
{
    struct LogFile *rfl;
    FILE *ofp;

    rfl=addRotatedLogFile(ffile->filename,filenum);
    if((ofp=openLogFile(rfl))){
        DBGL("parsing %s\n",ffile->filename);
        readThroughFFile(ffile,ofp);
        rfl->minstamp=ffile->minstamp;
        rfl->maxstamp=ffile->maxstamp;
        rfl->numlines=ffile->linenumber;
    }
}/* }}} */
int finishReadingRotatedFile( struct FollowedFile *ffile, int filenum )/* {{{1 */
{
    int ret=0;
    char *path;
    char *tmp;
    int len;
    int lene=0;
    int lenn=0;
    char *tfn=NULL;
    DIR *dp;
    struct dirent *rd;
    char *fql;
    char *dfql;
    char *holdfname;
    char *bf;
    struct LogFile *rfl;
    FILE *ofp;

    holdfname=ffile->filename;
    lenn=snprintf(tfn,0,"%d",filenum); /* get the length of filenum as a string */
    lene=snprintf(tfn,0,"%s",configValue("rotatedlogfileext")); /* the length of the r log file extension */
    len=strlen(ffile->filename);
    len+=lenn+lene+3; /* add 1 for each seperating . and 1 for the end of string */
    if((fql=malloc(len))){
        if((dfql=malloc(len))){
            /* 
             * add .filenum to the followed filename as it has been rotated
             * and the rotated log file ext from the config file.
             */
            if(lene){
                len=snprintf(fql,len,"%s.%d.%s",ffile->filename,filenum,configValue("rotatedlogfileext"));
            }else{
                len=snprintf(fql,len,"%s.%d",ffile->filename,filenum);
            }
            /* copy of filename for dirname call */
            strcpy(dfql,fql);
            /* later on we'll need the new file name in the ffile struct */
            ffile->filename=fql;
            DBGL("new filename after rotation: %s",ffile->filename);
            /* get the path */
            tmp=dirname(dfql);
            if((path=strdup(tmp)) == NULL){
                CCAE(1,"out of memory");
            }
            if((dp=opendir(path)) == NULL){
                CCAE(errno,"Unable to open directory");
            }
            if((bf=bname(fql))){
                len=strlen(bf);
                DBGL("Basename: %s, len: %d",bf,len);
                while((rd=readdir(dp)) != NULL){
                    if((ret=strcmp(bf,rd->d_name)) == 0){
                        DBGL("compared to %s passed ok.",rd->d_name);
                        /*
                         * found the file we were following which has been rotated
                         * so, read through the file, until we get to the last line
                         * we read, and continue looking for the search string
                         * also, as we go, create the index and add this to the 
                         * beginning of the indexed log files
                         */
                        /*
                         * reuse fql so free it first
                         */
                        free(fql);
                        len=snprintf(fql,0,"%s/%s",path,rd->d_name);
                        len++;
                        if((fql=malloc(len))==NULL){
                            CCAE(1,"out of memory");
                        }
                        len=snprintf(fql,len,"%s/%s",path,rd->d_name);
                        DBGL("rotated file is now called %s",fql);
                        /*
                        DBG("incrementing log files");
                        incrementRotatedLogfiles();
                        DBG("finished incrementing");
                        rfl=addRotatedLogFile(fql,filenum);
                        if((ofp=openLogFile(rfl))){
                            DBG("reading through finish file");
                            readThroughFFile(ffile,ofp);
                            DBG("fisnished reading finish file");
                            rfl->minstamp=ffile->minstamp;
                            rfl->maxstamp=ffile->maxstamp;
                            rfl->numlines=ffile->linenumber;
                        }
                        */
                        /* found the file we were looking for so stop reading the directory */
                        break;
                    }else{
                        DBGL("compared to %s failed.",rd->d_name);
                    }
                }
                free(bf);
            }else{
                CCAE(1,"out of memory");
            }
            /* put the original file name back */
            ffile->filename=holdfname;
            free(fql);
            free(dfql);
        }else{
            CCAE(1,"out of memory");
        }
    }else{
        CCAE(1,"out of memory");
    }
    return ret;
}/* }}} */
void deleteOldestRotatedLogfile(void)/* {{{1 */
{
    struct LogFile *trlf;
    struct LogFile *prlf;
    long fsz=0;
    int dontfree=0;

    DBG("Finding last rotated log file");
    if(rotatedlogs){
        trlf=rotatedlogs->next;
        prlf=rotatedlogs;
        while(trlf->next){
            prlf=trlf;
            trlf=trlf->next;
        }
        /*
         * trlf is guaranteed to be the last one as they are
         * inserted into the list in numeric order
         * and prlf is the previous one.
         */
        if(trlf){
            DBGL("Last log file is %s",trlf->filename);
            if(trlf->filename){
                fsz=filesize(trlf->filename);
                if(fsz==-1){
                    /* file does not exist, ok to free this record */
                    DBGL("%s does not exist, freeing record",trlf->filename);
                    free(trlf->filename);
                }else{
                    DBGL("%s still exists, not freeing record",trlf->filename);
                    dontfree=1;
                }
            }
            if(dontfree==0){
                free(trlf);
            }
        }
        if(dontfree==0){
            prlf->next=NULL;
        }
    }
}/* }}} */
void incrementRotatedLogfiles(void)/* {{{1 */
{
    struct LogFile *trlf;
    struct LogFile *nrlf;
    char *firstfilename;
    int oktofree=0;

    /*
     * rather than dick around cutting up strings and incrementing numbers and putting strings back together
     * this will copy the next log file struct filename and number to this one
     * thus moving them up by one but keeping the referential integrity of the indexes.
     * so java.log.1.zip will have been rotated by the log rotate system to java.log.2.zip
     * and so our struct filename will now be correct.
     * this will leave a "gap" at the front of the list (that doesn't exist) ready to have
     * the freshly rotated and zipped followed file inserted into it.
     * the last log file in the list is deleted from the list as it will have been deleted from disk.
     */
    /*
     * ensure that we have some rotated logs to deal with first
     */
    if(rotatedlogs){
        trlf=rotatedlogs;
        firstfilename=rotatedlogs->filename;
        DBGL("first filename is %s",firstfilename);
        while(trlf){
            DBGL("This filename is %s",trlf->filename);
            if(trlf->next){
                oktofree=1;
                DBG("ok to free firstfilename");
                nrlf=trlf->next;
                DBGL("Next filename is %s",nrlf->filename);
                trlf->filename=nrlf->filename;
                trlf->filenum=nrlf->filenum;
            }
            trlf=trlf->next;
        }
        if(oktofree==1){
            DBG("freeing first filename");
            free(firstfilename);
        }
        deleteOldestRotatedLogfile();
    }
}/* }}} */
void freeRotatedLogFiles( void )/* {{{1 */
{
    struct LogFile *rlf;

    DBG("Freeing rotated logs");
    while((rotatedlogs)){
        rlf=rotatedlogs;
        rotatedlogs=rlf->next;
        if(rlf->filename){
            free(rlf->filename);
        }
        free(rlf);
    }
}/* }}} */
void printRotatedLogs(void)/* {{{1 */
{
    struct LogFile *trlf;
    if(rotatedlogs){
        trlf=rotatedlogs;
        while(trlf){
            printRLog(trlf);
            trlf=trlf->next;
        }
    }
}/* }}} */
void printRLog(struct LogFile *rlf)/* {{{1 */
{
    char *minstamp;
    char *maxstamp;
    char *spbuf;
    time_t junk;

    if( (spbuf=malloc(27)) == NULL){
        CCAC("Failed to allocate memory for building output string 5.");
    }else{
        junk=rlf->minstamp;
        spbuf=ctime_r(&junk,spbuf);
        minstamp=strdup(trim(spbuf));
        junk=rlf->maxstamp;
        spbuf=ctime_r(&junk,spbuf);
        maxstamp=strdup(trim(spbuf));
        printf("name: %s\nnum: %d\nsize: %ld\nlines: %ld\nstart: %s\nstop: %s\ntime: %d\ntype: %d\n\n",rlf->filename,rlf->filenum,rlf->filesize,rlf->numlines,minstamp,maxstamp,rlf->maxstamp-rlf->minstamp,rlf->type);
        free(maxstamp);
        free(minstamp);
        free(spbuf);
    }
}/* }}} */
